# -*- coding: utf-8 -*-
# created on 2016/8/15

from collections import defaultdict
from mathsolver.functions.base import *


class Process(object):
    def __init__(self):
        self.deduces = []
        self.depth = 0

    def append(self, deduce):
        self.deduces.append(deduce)


class GeoSolver(object):
    def __init__(self):
        self.intermediate = False
        self.conditions = {}
        self.equations = {}
        self.variables = {}
        self.depths = {}
        self.answer = None
        self.steps = []
        self.label = set()
        self.value = ""
        self.collinears = []

    def rule_001(self):
        # 判断全等三角型
        lines = [obj for obj in self.conditions if isinstance(obj, BaseLine)]
        eqs = [obj for obj in self.conditions if isinstance(obj, BaseGeoEq)]

        triangles = []
        if len(lines) > 3:
            for items in itertools.combinations(lines, 3):
                names = set("".join([line.name for line in items]))
                if len(names) == 3 and all([len(collinear.intersection(names)) != 3 for collinear in self.collinears]):
                    triangles.append(BaseTriangle("".join(names)))

        deductions = []
        if len(triangles) > 1:
            for tri1, tri2 in itertools.combinations(triangles, 2):
                conditions = []
                for eq in eqs:
                    is_line = isinstance(eq.objs[0], BaseLine) and isinstance(eq.objs[1], BaseLine)
                    is_angle = isinstance(eq.objs[0], BaseAngle) and isinstance(eq.objs[1], BaseAngle)
                    if is_line or is_angle:
                        if (tri1.has(eq.objs[0]) and tri2.has(eq.objs[1])) or (
                                tri1.has(eq.objs[1]) and tri2.has(eq.objs[0])):
                            conditions.append(eq)

                if len(conditions) >= 3:
                    for tri2_name in itertools.permutations(tri2.name, 3):
                        pairs = [set(p1 + p2) for p1, p2 in zip(tri1.name, tri2_name)]
                        is_match = True
                        for condition in conditions:
                            if isinstance(condition.objs[0], BaseAngle):
                                pair = set(condition.objs[0].middle + condition.objs[1].middle)
                                if pair not in pairs:
                                    is_match = False
                                    break
                            elif isinstance(condition.objs[1], BaseLine):
                                points = set(condition.objs[0].name + condition.objs[1].name)
                                if len([pair for pair in pairs if pair.intersection(points)]) != 2:
                                    is_match = False
                                    break

                        if is_match:
                            deduction = BaseTriEq(None, *[tri1, BaseTriangle("".join(tri2_name))])
                            deduction.label = "证全等三角型"
                            deductions.append((deduction, conditions[:3]))
                            break

        return deductions

    def deduce(self):
        return self.rule_001()

    def append(self, cond, source="root"):
        if cond not in self.conditions:
            self.conditions[cond] = source

            if isinstance(cond, GeoExpr):
                subs = {}
                for obj in cond.objs:
                    if isinstance(obj, (BaseAngle, BaseLine)):
                        if obj not in self.variables:
                            subs[obj] = sympify(obj.name)
                        else:
                            subs[obj] = self.variables[obj]

                    if isinstance(obj, BaseGeo) and obj not in self.conditions:
                        self.conditions[obj] = source

                self.variables.update(subs)
                for equation in cond.sympify():
                    if isinstance(equation, Eq):
                        equation = equation.subs([(obj.sympify(), var) for obj, var in self.variables.items()])
                        self.equations[equation] = cond

            if isinstance(cond, BaseCollinear):
                name_set = set(cond.objs[0] + cond.objs[1].name)
                not_in = True
                for collinear in self.collinears:
                    if name_set.intersection(collinear):
                        collinear.update(name_set)
                        not_in = False

                if not_in:
                    self.collinears.append(name_set)

        # collinears = [cond for cond in self.conditions if isinstance(cond, baseCollinear)]
        angles = [cond for cond in self.conditions if isinstance(cond, BaseAngle)]
        for angle in angles:
            angle.update(self.collinears)

    def is_solved(self, solveset, query):
        if isinstance(query, BaseAngle):
            cands = []
            for ans in solveset:
                if query == str(ans) and len(solveset[ans].free_symbols) == 0:
                    cands.append(solveset[ans])

                if len(set(cands)) == 1:
                    if not self.answer:
                        self.answer = BaseGeoEq(None, query, cands.pop())
                    return True
            return False

    def filter(self, equations, query):
        for i in range(1, len(equations)):
            sub_equations = equations[:i - 1] + equations[i + 1:]
            solveset = solve(sub_equations)
            if solveset and isinstance(solveset, list):
                solveset = solveset[0]

            if solveset and self.is_solved(solveset, query):
                return sub_equations

        return []

    def run(self, query):
        depth = 0
        if isinstance(query, GeoExpr):
            for obj in query.objs:
                if isinstance(obj, BaseGeo):
                    self.conditions[obj] = "root"

        while True:
            new_conds = []
            for cond in self.conditions:
                if not cond.deduced:
                    for new_cond in cond.deduce(self.conditions):
                        if new_cond not in self.conditions:
                            new_conds.append((new_cond, cond))

            new_conds.extend(self.deduce())

            for new_cond, cond in new_conds:
                self.append(new_cond, cond)

            if isinstance(query, GeoExpr):
                if query in self.conditions:
                    self.answer = query
                    break
            elif self.is_solved(solve(self.equations.keys()), query):
                break

            if not new_conds:
                raise ValueError("Unsolvable!")

            depth += 1

        if isinstance(query, BaseGeo):
            equations = self.equations.keys()
            while True:
                new_equations = self.filter(equations, query)
                if not new_equations:
                    break
                else:
                    equations = new_equations

            conditions = [self.equations[eq] for eq in equations]
            source_dict = defaultdict(Process)
            depth = 0
            while True:
                new_source = defaultdict(Process)
                for cond in conditions:
                    source = self.conditions[cond]
                    new_source[source].append(cond)
                    new_source[source].depth = depth

                conditions = [source for source in new_source if source != "root"]
                if not conditions:
                    break

                source_dict.update(new_source)
                depth += 1

            roots = source_dict.pop("root")
            sort_by_depth = sorted(source_dict.items(), key=lambda x: x[1].depth, reverse=True)

        elif isinstance(query, GeoExpr):
            conditions = [cond for cond in self.conditions if cond == query]
            source_dict = defaultdict(Process)
            depth = 0
            while True:
                new_source = defaultdict(Process)
                for cond in conditions:
                    source = self.conditions[cond]
                    if source == "root":
                        source = ("root",)
                        continue
                    elif isinstance(source, list):
                        source = tuple(source)
                    else:
                        source = (source,)

                    new_source[source].append(cond)
                    new_source[source].depth = depth

                conditions = [s for s in source if s != "root"]
                if not conditions:
                    break

                source_dict.update(new_source)
                depth += 1

            roots = source_dict.pop(("root",)) if ("root",) in source_dict else []
            sort_by_depth = sorted(source_dict.items(), key=lambda x: x[1].depth, reverse=True)

        for source, process in sort_by_depth:
            if not isinstance(source, (tuple, list)):
                source = [source]
            cond = "∵%s" % ",".join([s.printing() for s in source])  # source.printing()
            deduces = "∴%s" % ",".join([deduce.printing() for deduce in process.deduces])
            steps = [[cond, deduces]]
            label = list(set([deduce.label for deduce in process.deduces]))
            self.steps.append({"labels": label, "steps": steps})

        if roots:
            steps = [["∵%s" % ",".join([deduce.printing() for deduce in roots.deduces]),
                      "∴%s" % self.answer.printing()]]
            label = ["代数运算"]
            self.steps.append({"labels": label, "steps": steps})


if __name__ == '__main__':
    geosolver = GeoSolver()
    geosolver.append(BaseTriEq(["ABC", "≌", "ADC"]))  # ABC, ≌, ADCTriEq(Triangle("ABC"), Triangle("ADC")))
    geosolver.append(BaseGeoEq(["Angle(BAC)", "60"]))  # AngleEq(Angle("BAC"), 60))
    geosolver.append(BaseGeoEq(["Angle(ACB)", "25"]))  # AngleEq(Angle("ACB"), 25))
    geosolver.run(BaseAngle("D"))
    # for step in geosolver.steps:
    #     print step["labels"]
    #     for a,b in step["steps"]:
    #         print a,b
    #
    # print geosolver.label

    # geosolver.append(baseTriangle("ABC"))#ABC, ≌, ADCTriEq(Triangle("ABC"), Triangle("ADC")))
    # geosolver.append(baseGeoeq(["Angle(ACB)", "90"]))#AngleEq(Angle("BAC"), 60))
    # geosolver.append(baseGeoeq(["Angle(ABC)", "25"]))#AngleEq(Angle("ACB"), 25))
    # geoSolver.append(baseVertical(["Line(CD)", "Line(AB)"]))
    # geosolver.run(BaseAngle("ACD"))
    # for a,b in geosolver.steps:
    #     print a,b

    # geosolver.append(baseCollinear(["E", "Line(AB)"]))
    # geosolver.append(baseCollinear(["F", "Line(AB)"]))
    # geosolver.append(baseParallel(["Line(AC)", "Line(BD)"]))
    # geosolver.append(baseGeoeq(["Line(AE)", "Line(BF)"]))
    # geosolver.append(baseGeoeq(["Angle(C)", "Angle(D)"]))
    # geosolver.run(baseGeoeq(["Line(CF)", "Line(DE)"]))

    # geosolver.append(baseCollinear(["E", "Line(AB)"]))
    # geosolver.append(baseCollinear(["F", "Line(AB)"]))
    # geosolver.append(baseGeoeq(["Angle(C)", "Angle(D)"]))
    # #geosolver.append(baseGeoeq(["Angle(CAB)", "Angle(ABD)"]))
    # geosolver.append(baseGeoeq(["Line(AE)", "Line(BF)"]))
    # geosolver.append(baseParallel(["Line(AC)", "Line(BD)"]))
    # geosolver.run(baseGeoeq(["Line(CF)", "Line(DE)"]))
    # for step in geosolver.steps:
    #     print step["labels"]
    #     for a,b in step["steps"]:
    #         print a,b
    # geosolver.append(baseLine("CF"))
    # geosolver.append(baseLine("DE"))

    # deduction = geosolver.rule_001()[0][0].deduce([])
    # query = baseGeoeq(["Line(CF)", "Line(DE)"])
    # print query in deduction
    #
    # print list(geosolver.rule_001()[0][0].deduce([]))
    # print geosolver.label
